package com.yeskery.nut.scan.advice;

import com.yeskery.nut.bean.NoSuchBeanException;
import com.yeskery.nut.core.*;
import com.yeskery.nut.extend.responsive.*;
import com.yeskery.nut.plugin.BindContext;
import com.yeskery.nut.plugin.ExceptionHandlePlugin;
import com.yeskery.nut.util.ReflectUtils;
import com.yeskery.nut.util.StringUtils;
import com.yeskery.nut.view.ViewHandler;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * ExceptionHandler注解拦截器
 * @author sprout
 * 2022-06-15 13:43
 */
public class ExceptionHandlerPlugin extends BaseControllerAdviceAnnotationHandler implements ExceptionHandlePlugin {

    /** 日志对象 */
    private static final Logger logger = Logger.getLogger(ExceptionHandlerPlugin.class.getName());

    /** 异常处理器 */
    private final List<ExceptionHandlerMetadata> metadataList = new ArrayList<>();

    /**
     * 注册异常
     * @param throwableClasses 异常对象类数组
     * @param produces 指定向客户端返回的媒体类型
     * @param basePackages 要处理的基础包
     * @param metadataAttributes 注解请求方法属性
     */
    public void registerThrowable(Class<? extends Throwable>[] throwableClasses, String[] produces, String[] basePackages,
                                  ControllerAdviceCombinationMetadataAttributes metadataAttributes) {
        if (throwableClasses == null || throwableClasses.length == 0) {
            registerResource(basePackages, metadataAttributes);
        } else {
            for (Class<? extends Throwable> throwableClass : throwableClasses) {
                if (metadataList.isEmpty()) {
                    metadataList.add(new ExceptionHandlerMetadata(basePackages, throwableClass, produces, metadataAttributes));
                } else {
                    for (int i = 0; i < metadataList.size(); i++) {
                        ExceptionHandlerMetadata exceptionHandlerMetadata = metadataList.get(i);
                        if (exceptionHandlerMetadata.throwableClass.isAssignableFrom(throwableClass)) {
                            if (exceptionHandlerMetadata.throwableClass == throwableClass) {
                                throw new NutException("Throwable [" + throwableClass.getName() + "] Already Bind.");
                            }
                            metadataList.add(i, new ExceptionHandlerMetadata(basePackages, throwableClass, produces, metadataAttributes));
                            return;
                        }
                    }
                    metadataList.add(new ExceptionHandlerMetadata(basePackages, throwableClass, produces, metadataAttributes));
                }
            }
        }
    }

    @Override
    public boolean handleException(Controller controller, Request request, Response response, Execution execution, Exception e) {
        ExceptionHandlerMetadata metadata = null;
        String controllerPackageName = getControllerPackageName(controller);

        for (ExceptionHandlerMetadata exceptionHandlerMetadata : metadataList) {
            if (exceptionHandlerMetadata.throwableClass.isAssignableFrom(e.getClass())) {
                if (exceptionHandlerMetadata.basePackages.length == 0) {
                    metadata = exceptionHandlerMetadata;
                    break;
                } else {
                    final String tempControllerPackageName = controllerPackageName;
                    if (Arrays.stream(exceptionHandlerMetadata.basePackages)
                            .anyMatch(p -> StringUtils.isEmpty(tempControllerPackageName) || tempControllerPackageName.startsWith(p))) {
                        metadata = exceptionHandlerMetadata;
                        break;
                    }
                }
            }
        }

        // 尝试从默认包异常处理中进行查找
        if (metadata == null) {
            for (Map.Entry<String, ControllerAdviceCombinationMetadataAttributes> entry : packageDefaultMetadataMap.entrySet()) {
                if (StringUtils.isEmpty(controllerPackageName) || controllerPackageName.startsWith(entry.getKey())) {
                    metadata = new ExceptionHandlerMetadata(new String[0], Throwable.class, new String[0], entry.getValue());
                    break;
                }
            }
        }

        // 尝试从全局默认包异常处理中进行查找
        if (metadata == null) {
            ControllerAdviceCombinationMetadataAttributes metadataAttributes = packageDefaultMetadataMap.get(DEFAULT_EMPTY_PACKAGES);
            if (metadataAttributes != null) {
                metadata = new ExceptionHandlerMetadata(new String[0], Throwable.class, new String[0], metadataAttributes);
            }
        }

        if (metadata == null) {
            return false;
        }
        Method method = metadata.metadataAttributes.getMethod();
        Parameter[] parameters = method.getParameters();
        Object[] params = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            Class<?> type = parameters[i].getType();
            if (type.isAssignableFrom(Request.class)) {
                params[i] = request;
            } else if (type.isAssignableFrom(Session.class)) {
                params[i] = request.getSession();
            } else if (type.isAssignableFrom(Response.class)) {
                params[i] = response;
            } else if (type.isAssignableFrom(Execution.class)) {
                params[i] = execution;
            }  else if (type.isAssignableFrom(Forward.class)) {
                params[i] = execution.getForward();
            } else if (type.isAssignableFrom(BindContext.class)) {
                params[i] = execution.getBindContext();
            } else if (type.isAssignableFrom(ViewHandler.class)) {
                params[i] = execution.getViewHandler();
            } else if (type.isAssignableFrom(Model.class)) {
                params[i] = Model.DEFAULT_MODEL;
            } else if (type.isAssignableFrom(metadata.throwableClass)) {
                params[i] = e;
            }
        }

        Class<?> type = metadata.metadataAttributes.getType();
        Object target = ReflectUtils.getTargetByCache(getApplicationContext(), targetMap, type);

        try {
            Object result = method.invoke(target, params);
            if (metadata.metadataAttributes.isResponseBody()) {
                Responsive responsive;
                String[] produces;
                if ((produces = metadata.produces) != null && produces.length > 0 && MediaType.APPLICATION_XML.getValue().equals(produces[0])) {
                    try {
                        responsive = execution.getBindContext().getObject(XmlResponsive.class);
                    } catch (NoSuchBeanException e1) {
                        throw new NutException("Use Annotation [@RequestBody] And Set Request Consume[" + MediaType.APPLICATION_XML.getValue()
                                + "], But Unable Find Plugin[" + XmlResponsivePlugin.class.getName() + "]");
                    }
                } else {
                    try {
                        responsive = execution.getBindContext().getObject(JsonResponsive.class);
                    } catch (NoSuchBeanException e1) {
                        throw new NutException("Use Annotation [@RequestBody] And Set Request Consume[" + MediaType.APPLICATION_JSON.getValue()
                                + "], But Unable Find Plugin[" + JsonResponsivePlugin.class.getName() + "]");
                    }
                }
                if (result == null) {
                    response.writeEmpty();
                } else {
                    responsive.writeBody(result);
                }
            }

            // 日志打印
            logger.logp(Level.WARNING, ExceptionHandlerPlugin.class.getName(), "handleException",
                    "Exception Handle.", e);
            return true;
        } catch (Exception e1) {
            throw new ProxyObjectInvokeException("Target Method Execute Fail.", e1);
        }
    }

    /**
     * ExceptionHandler元数据
     * @author sprout
     * 2022-06-15 15:04
     */
    private static class ExceptionHandlerMetadata {
        /** 基础包 */
        private final String[] basePackages;
        /** 异常对象类 */
        private final Class<? extends Throwable> throwableClass;
        /** 指定向客户端返回的媒体类型 */
        private final String[] produces;
        /** 组合元数据 */
        private final ControllerAdviceCombinationMetadataAttributes metadataAttributes;

        /**
         * 构建ExceptionHandler元数据
         * @param basePackages 基础包
         * @param throwableClass 异常对象类
         * @param metadataAttributes 组合元数据
         */
        private ExceptionHandlerMetadata(String[] basePackages, Class<? extends Throwable> throwableClass, String[] produces,
                                         ControllerAdviceCombinationMetadataAttributes metadataAttributes) {
            this.basePackages = basePackages;
            this.throwableClass = throwableClass;
            this.produces = produces;
            this.metadataAttributes = metadataAttributes;
        }
    }
}
